
#include <iostream>
#include <list>
#include <algorithm>

template <bool StoreProperty, typename Value, typename... Properties>
class elimination_tree_node
{

};

template <bool StoreProperty, typename Value>
class elimination_tree_node<StoreProperty, Value>
{
private:
	Value _val;
public:
	elimination_tree_node(Value &&value)
		:_val(std::forward<Value>(value))
	{

	}

	elimination_tree_node(const Value &value)
		:_val(value)
	{

	}

	void execute()
	{
		_val();
	}
};

namespace impl
{
	template <typename PropertiesTuple>
	struct _elimination_tree_node_insert_helper;

	template <typename HeadProperty, typename... TailProperties>
	struct _elimination_tree_node_insert_helper<std::tuple<HeadProperty, TailProperties...>>
	{
		template <typename DownstreamContainer, typename Value>
		static void insert(
			DownstreamContainer &downstream,
			Value &&value)
		{
			auto nextProperty = value.property<HeadProperty>();
			auto r = std::find_if(downstream.begin(), downstream.end(),
				[&](auto &v) { return v._getProp() == nextProperty; });
			if (r == downstream.end())
				r = downstream.insert(downstream.end(), { std::move(nextProperty) });
			r->insert({ value });
		}

		template <typename DownstreamContainer, typename Value>
		static void insert(
			DownstreamContainer &downstream,
			const Value &value)
		{
			auto nextProperty = value.property<HeadProperty>();
			auto r = std::find_if(downstream.begin(), downstream.end(),
				[&](auto &v) { return v._getProp() == nextProperty; });
			if (r == downstream.end())
				r = downstream.insert(downstream.end(), { std::move(nextProperty) });
			r->insert({ value });
		}
	};

	template <>
	struct _elimination_tree_node_insert_helper<std::tuple<>>
	{
		template <typename DownstreamContainer, typename Value>
		static void insert(
			DownstreamContainer &downstream,
			Value &&value)
		{
			downstream.insert(downstream.end(), { value });
		}

		template <typename DownstreamContainer, typename Value>
		static void insert(
			DownstreamContainer &downstream,
			const Value &value)
		{
			downstream.insert(downstream.end(), { value });
		}
	};

	struct _property_less_node
	{
		template <typename... Args>
		_property_less_node(Args&&... args)
		{

		}

		void execute()
		{

		}

		void post_execute()
		{

		}
	};

	template <typename Ty>
	struct _propertied_node
	{
	protected:
		Ty _val;

		template <typename... Args>
		_propertied_node(Args&&... args)
			:_val(std::forward<Args...>(args...))
		{

		}

		void execute()
		{
			_val();
		}

		template<class T>
		auto _optinalPostExec(T &proper) -> decltype(proper.post_execute())
		{
			return proper.post_execute();
		}

		auto _optinalPostExec(...) -> void
		{

		}

		void post_execute()
		{
			_optinalPostExec(_val);
		}
	};
}

template <bool StoreProperty, typename Value, typename HeadProperty, typename... TailProperties>
class elimination_tree_node<StoreProperty, Value, HeadProperty, TailProperties...>
	:std::conditional<StoreProperty, impl::_propertied_node<HeadProperty>, impl::_property_less_node>::type
{
	typedef typename std::conditional<
		StoreProperty,
		impl::_propertied_node<HeadProperty>,
		impl::_property_less_node
	>::type MyOptionalProperty;

	typedef typename std::conditional<
		StoreProperty,
		elimination_tree_node<true, Value, TailProperties...>,
		elimination_tree_node<true, Value, HeadProperty, TailProperties...>
	>::type MyDownstreamNode;

	typedef typename std::conditional<
		StoreProperty,
		std::tuple<TailProperties...>,
		std::tuple<HeadProperty, TailProperties...>
	>::type MyDownstreamAttribsTuple;

	template <typename>
	friend struct impl::_elimination_tree_node_insert_helper;
private:
	std::list<MyDownstreamNode> _downstream;

	const HeadProperty& _getProp() const
	{
		return _val;
	}

public:
	typedef HeadProperty property_type;

	elimination_tree_node()
	{
		static_assert(!StoreProperty,
			"There is no default construtor provided for "
			"a node that stores property.");
	}

	elimination_tree_node(HeadProperty &&prop)
		:MyOptionalProperty(std::forward<HeadProperty>(prop))
	{

	}

	void insert(Value &&value)
	{
		impl::_elimination_tree_node_insert_helper<MyDownstreamAttribsTuple>::
			insert(_downstream, std::forward<Value>(value));
	}

	void insert(const Value &value)
	{
		impl::_elimination_tree_node_insert_helper<MyDownstreamAttribsTuple>::
			insert(_downstream, value);
	}

public:

	void execute()
	{
		MyOptionalProperty::execute();
		for (auto &node : _downstream)
			node.execute();
		post_execute();
	}
};

template <typename Value, typename... Properties>
class elimination_tree
{
private:
	elimination_tree_node<false, Value, Properties...> _root;
public:
	void execute()
	{
		_root.execute();
	}

	void insert(Value &&value)
	{
		_root.insert(std::forward<Value>(value));
	}

	void insert(const Value &value)
	{
		_root.insert(value);
	}
};